/*
 * Copyright 2008 Neil Bacon
 * Distributed under the terms of the Mozilla Public License version 1.1
 * or the GNU General Public License version 3.
 */

package nb.xadd.operator.impl;

import nb.cache.CacheMap;
import nb.xadd.XADDRuntimeException;
import nb.xadd.node.Node;
import nb.xadd.node.NodeFactory;
import nb.xadd.node.impl.AbstractDecisionNode;
import nb.xadd.node.impl.BooleanDecisionNode;
import nb.xadd.node.impl.RealEqualDecisionNode;
import nb.xadd.node.impl.RealGreaterOrEqualDecisionNode;
import nb.xadd.operator.BinaryOperator;
import nb.xadd.operator.OperatorFactory;
import nb.xadd.operator.UnaryOperator;

/**
 * Correct the order of decision nodes.
 */
public class Sort implements UnaryOperator {

    //	private Log log = LogFactory.getLog(getClass());

    private final NodeFactory nodeFactory;
    private final CacheMap<UnaryOperatorCacheKey, Node> unaryOperatorCache;

    private final Node zero;
    private final Node one;

    private final BinaryOperator plus;
    private final BinaryOperator times;

    protected Sort(NodeFactory nodeFactory, OperatorFactory operatorFactory, CacheMap<UnaryOperatorCacheKey, Node> unaryOperatorCache) {
        super();
        this.nodeFactory = nodeFactory;
        this.unaryOperatorCache = unaryOperatorCache;

        zero = nodeFactory.getConstantValueNode(0.0);
        one = nodeFactory.getConstantValueNode(1.0);

        plus = operatorFactory.getAddition();
        times = operatorFactory.getMultiplication();
    }

    public Node execute(Node n) {
        Node result = n;
        if (n instanceof AbstractDecisionNode) {
            UnaryOperatorCacheKey key = new UnaryOperatorCacheKey(this, n);
            result = unaryOperatorCache.get(key);
            if (result == null) {
                result = walk(n);
                unaryOperatorCache.put(key, result);
            }
        }
        return result;
    }

    private Node walk(Node n) {
        Node result = n;
        if (n instanceof AbstractDecisionNode) {
            AbstractDecisionNode dn = (AbstractDecisionNode) n;
            Node trueChild = walk(dn.getTrueChild());
            Node falseChild = walk(dn.getFalseChild());

            Node nTrue;
            Node nFalse;
            if (n instanceof RealGreaterOrEqualDecisionNode) {
                RealGreaterOrEqualDecisionNode rdn = (RealGreaterOrEqualDecisionNode) n;
                nTrue = nodeFactory.getRealGreaterOrEqualDecisionNode(rdn.getRealVarIndex(), rdn.getTestValue(), one, zero);
                nFalse = nodeFactory.getRealGreaterOrEqualDecisionNode(rdn.getRealVarIndex(), rdn.getTestValue(), zero, one);
            } else if (n instanceof RealEqualDecisionNode) {
                RealEqualDecisionNode rdn = (RealEqualDecisionNode) n;
                nTrue = nodeFactory.getRealEqualDecisionNode(rdn.getRealVarIndex(), rdn.getTestValue(), one, zero);
                nFalse = nodeFactory.getRealEqualDecisionNode(rdn.getRealVarIndex(), rdn.getTestValue(), zero, one);
            } else if (n instanceof BooleanDecisionNode) {
                BooleanDecisionNode bdn = (BooleanDecisionNode) n;
                nTrue = nodeFactory.getBooleanDecisionNode(bdn.getBooleanVarIndex(), one, zero);
                nFalse = nodeFactory.getBooleanDecisionNode(bdn.getBooleanVarIndex(), zero, one);
            } else {
                throw new XADDRuntimeException("Unhandled decision node: type = " + n.getClass().getName() + ", node = " + n);
            }

            Node truePart = trueChild.apply(times, nTrue);
            Node falsePart = falseChild.apply(times, nFalse);
            //			if (log.isDebugEnabled()) {
            //				log.debug("walk: nTrue = " + nTrue
            //						+ "\nnFalse = " + nFalse
            //						+ "\ntrueChild = " + trueChild
            //						+ "\ntruePart = " + truePart
            //						+ "\nfalseChild = " + falseChild
            //						+ "\nfalsePart = " + falsePart
            //				);
            //			}
            result = truePart.apply(plus, falsePart);
        }
        return result;
    }

}
